#include<iostream>
using namespace std;


//一丶 抽象类
//1.概念:
//在虚函数后面写上=0,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类)
// ----抽象类不能实例化对象,派生类继承后也不能实例化对象,只有重写纯虚函数之后,派生类才能够实例化对象.
//纯虚函数规范了派生类必须重写,另外纯虚函数也体现出了了接口继承


//2.纯虚函数举例子
//class person      //这个就是抽象类   因为不能实例化出对象
//{
//public:
//     virtual void test()=0;            //这就是纯虚函数,等于就提供了一个函数声明,因此派生类继承个声明当然没法实例化对象     -----所以派生类必须重写这个纯虚函数才能记性实例化
//     //纯虚函数是只声明不实现的,虽然能实现(按正常实现就可,win系统下的编译器可以实现  linux下不行,linux可以在类的外面定义,但是依然没什么用),但是实现出来并没有什么意义
////    {
////        cout<<"person"<<endl;
////    }    //linux平台下的编译器会直接报错
//
//
//    void test1()
//    {
//         cout<<"test1"<<endl;
//    }
//};
//
//void person::test()
//{
//    cout<<"person"<<endl;
//}//在person类的外面定义出这个虚函数
//
//class student:public person
//{
//public:
//    void test()
//    {
//        cout<<"student"<<endl;
//    }
//};
//
//int main(void)
//{
////    person* p1=nullptr;          //空指针的原因,因为这个不能实例化对象,所以也就不能new出来一块空间
////    p1->test();//为什么调不到这个纯虚函数?和虚表有关系,哪怕是被定义过,掉了会崩
////    p1->test1();
//
//
//    //子类重写之后就能调用
//    person* p1=new student;
//    p1->test();//这个不能调用的原因是要找到虚表中函数的地址
//    p1->test1();//看到下面的解释就知道了,普通函数是存放在常量区的,
//}




//3.抽象类的举例场景              ----纯虚函数的类,本质上强制了子类必须完成虚函数的重写
//抽象的意思就是:就是在现实世界当中没有对应的实物

//一个类型,如果一般在现实世界中,没有具体的对应实物就定义成抽象类比较好
// ---override只是在语法上检测是否完成重写

//继承实现的是实现继承         纯虚函数体现出的是接口继承



//4.接口继承和实现继承
//普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承是函数的实现
//虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口,所以如果不实现多态,不要把函数定义成虚函数











//二丶 多态的原理

//1.引例
//class base//这个类型的大小是16字节(这是64位测试出来的,32位是12)    为什么不是按照结构体字节对齐然后是8?        因为在监视器可以看出来多了一个表指针()
//        //多出来的这个成员叫虚拟表指针    _vfptr         virtual   form(表格)    ptr指针
//        //并且这个指针在内存中是存放在偏移量为0的地址处     优先存放虚函数表指针
//        //8 + 4 +1    然后最大对齐数整数倍 --->  16
//{
//public:
//    virtual void test1()
//    {
//        cout<<"test1()"<<endl;
//    }
//
//    virtual void test2()
//    {
//        cout<<"test2()"<<endl;
//    }
//    //
//
//private:
//    int _a;
//    char _c;
//};
//
//int main(void)
//{
//    cout<<sizeof(base)<<endl;//只有一个虚函数的情况下大小是16          在增加了一个虚函数之后其大小依然是16
//    //这是因为,多出来的内存保存的是一个虚拟表指针(可以理解为是一个指针变量,不是数组名)       而这个虚拟表指针本质上是一个数组指针     这个指针指向虚拟表,表中存放的都是虚函数的地址
//}

//虚拟表的本质是函数指针数组             普通函数是存放在常量区的     虚函数表指针简虚表指针
//找这个表是为了为了多态的实现




//2.通过例子讲解多态的原理                -----这个地方会有专属的word配套讲解
//class person
//{
//public:
//    virtual void BuyTicket()
//    {
//        cout<<"全价"<<endl;
//    }
//
//    void f()//这个函数的出现就是为了方便对比
//    {
//        cout<<"f()"<<endl;
//    }
//protected:
//    int _a=0;
//};
////父类实例化之后就会在内存生成一个虚表指针,并且这个虚函数的地址是存放在一个虚函数数组中的,然后这个虚表指针就指向这个数组     虚表指针本质上是一个数组指针
////数组本质上是一个虚函数指针数组
////如果有多个虚函数,则这个数组中就会存放多个虚函数的地址
//
//
////class student:public person
////        //子类实例化之后,虽然继承了父类,但是虚表是不同的,  其数组中存放的是重写后的虚函数(虚函数的重写也叫做覆盖)
////        //重写指的是语法层的概念,覆盖指的是原理层的概念
////{
////public:
////    virtual void BuyTicket()
////    {
////        cout<<"半价"<<endl;
////    }
////
////protected:
////    int _b=1;
////};
//
//void Func(person& p)
//{
//    p.BuyTicket();//请严格记住构成多态的条件:  用基类的指针或者引用来调用虚函数     被调用的函数必须是虚函数,并且基类的派生类一定要对虚函数进行重写,一定要有派生类
//}
//
//int main(void)
//{
//    person mike;
//    Func(mike);
//
////    student john;
////    Func(john);
////
////    person& p1=john;//word中还会将引用和对象进行对比区别
////    person p2=john;
////
////    p2.BuyTicket();//这样调用虽然能成功,但是不构成多态,这属于直接调用了
//
//    //
//    return 0;
//}

//首先有一个问题,多态(动态,后续笔记中的多态基本都是指的动态的多态)为什么必须是父类的指针和引用呢?
//---首先,我们要知道普通的函数调用都是直接确定地址
//---其次,配套的word有详细的图片讲解




//3.小小的总结:
// 多态调用在编译时不能确定调用的虚函数是哪个

//子类的虚函数哪怕是私有成员也能调到,从某种程度来说破坏了私密性

//虚函数的重写也叫做覆盖的原因是因为,子类对象会将虚表拷贝一份,然后讲自己的虚函数地址放在这个拷贝的表上,完成虚函数地址的覆盖

//!!!虚表中存放的是虚函数的指针,并不是存放的虚函数,虚函数和普通函数是一样的,都是存放在代码段的
//---而且对象中存的不是虚表,而是虚表指针,虚表指针是和对象所处同一区域

//---由于多个同类型对象的虚函数表都是相同的,因此虚函数表是肯定不会存在栈上的,存放在常量区的

//虚函数表存的是虚函数的地址  (其实底层虚函数表中其实存放的是指向虚函数的指针 的地址,是指针的地址,不过就认为是虚函数地址就行,了解一下即可)










//三丶单继承的时候的虚函数表
//class base
//{
//public:
//    virtual void func1(){;}
//    virtual void func2(){;}
//};
//
//class derive:public base
//{
//public:
//    virtual void func1(){;}
//    virtual void func3(){;}
//    virtual void func4(){;}
//};
//
////func3 func4这两会被藏起来,但是还会在虚表中存在   并且在vs下虚表的最后的元素后面会被添加上一个nullptr







//四丶多继承下的虚函数表
//class base1
//{
//public:
//    virtual void func1(){;}
//    virtual void func2(){;}
//};
//
//class base2
//{
//public:
//    virtual void func1(){;}
//    virtual void func4(){;}
//};
//
//class derive:public base1,base2
//{
//public:
//    virtual void func1(){;}//两个重写的func1是不一样的
//    virtual void func3(){;}
//};
//此时derive对象会有两张虚表,但是func3会放在哪个虚表?会放在第一个继承的虚表当中

//多继承时,子类重写了base1和base2的虚函数func1,但是虚表中重写的func1的地址是不一样的,但是没关系,他们最终调到的还是同一个函数

//二刷,他奶奶滴,必须二刷,这鬼视频一遍看不懂